#include <wx/wx.h>
#include <wx/utils.h>
#include <wx/filename.h>
#include <wx/dir.h>
#include <wx/zipstrm.h>
#include <wx/wfstream.h>
#include <wx/filesys.h>
#include <wx/wxsqlite3.h>
#include <wx/wxhtml.h>

#include "dictmgr.h"
#include "cfgmgr.h"
#include "common.h"
#include "dictquery.h"
#include "../utils/utils.h"


/* FLOW
 * -> Cons -> _find_new_or_del_dicts -> install_dict[N] or uninstall_dict[N]
 *        |
 *        |-> _create_all_dicts_dbconn
 * 
 * -> install_dicts -> _delete_all_dicts_dbconn -> _add_overwrite_file -> install_dict[M] -> _create_all_dicts_dbconn
 * 
 * -> uninstall_dicts -> _delete_all_dicts_dbconn -> _delete_file -> uninstall_dict[M] -> _create_all_dicts_dbconn
 * 
 * delete or install/reinstall condition: new or del file, 
 *                       already exist dict mark as -2/2;
 * 
 */

DictManager::DictManager() {
	wxLogMessage(_T("DictManager::DictManager"));
	
    // init Global Dict Directory and dict manage datebase
	dict_dir = wxFileName(ConfigManager::get().global_data_dir);
	dict_dir.AppendDir(_T("dict"));

	_dictmgr_db_fn = wxFileName(ConfigManager::get().user_data_dir);
	_dictmgr_db_fn.SetFullName(_T("dictmgr.db"));
    
    try { // status :
	      //   -2 -- need remove;
    	  //   -1 -- disabled;
    	  //    1 -- enabled;
  	      //    2 -- need install/reinstall (not sucessful, so must remove)
    	_dictmgr_db.Open(_dictmgr_db_fn.GetFullPath());
        
		_dictmgr_db.ExecuteUpdate(_T("CREATE TABLE IF NOT EXISTS dicts( \
        						id INTEGER PRIMARY KEY, \
                                name NOT NULL, \
                                filename NOT NULL, \
								status NOT NULL, \
								xorder \
                              );"
                         )
        );
		
    	_dictmgr_db.ExecuteUpdate(wxT("CREATE TABLE IF NOT EXISTS words ( \
        						id INTEGER PRIMARY KEY, \
                                word NOT NULL, \
                                lword NOT NULL, \
                                dictids TEXT NOT NULL \
                              );"
                         ));
    	
    	_dictmgr_db.ExecuteUpdate(wxT("CREATE UNIQUE INDEX IF NOT EXISTS words_index \
    							ON words ( \
    								word ASC \
                              	);"
                         ));
    	
    	_dictmgr_db.ExecuteUpdate(wxT("CREATE INDEX IF NOT EXISTS lwords_index \
    							ON words ( \
    								lword ASC \
                              	);"
                         ));
    	
    } catch (wxSQLite3Exception& e) {
    	wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
    	__DAO_FATAL_ERROR__;
    }
    

    
    reinit_installed_dict_info();
}


bool DictManager::create_all_dicts_dbconn() {
    // Find and connect enabled dicts
	try {
	    wxSQLite3ResultSet dicts_ret = _dictmgr_db.ExecuteQuery(
	    		_T("SELECT filename FROM dicts WHERE status=1;")
	            );
	    
	    while (dicts_ret.NextRow()) {
	    	wxString dictfn = dicts_ret.GetString(0);
	    	dict_dir.SetFullName(dictfn);
	    	
	        DictQuery* p_dictquery = new DictQuery(dict_dir.GetFullPath());
	        
	        _dict_query_hash[dictfn] = p_dictquery;
	    }
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		__DAO_FATAL_ERROR__;
		return false;
	}
	
	return true;
}

bool DictManager::delete_all_dicts_dbconn() {
	DictQueryHash::iterator it;
	for (it=_dict_query_hash.begin(); it!=_dict_query_hash.end(); it++) {
	    delete it->second;
	}
	
	_dict_query_hash.clear();
	
	return true;
}


bool DictManager::install_dicts(const wxString& zippath) {
	delete_all_dicts_dbconn();
	
	// unzip files
    wxFFileInputStream in(zippath);
    wxZipInputStream zip(in);
	
    wxFileSystem vfs;

    wxZipEntry* p_entry;
    while (p_entry=zip.GetNextEntry()) {
    	wxString name = p_entry->GetName();
    	
    	if ( !name.Contains(_T(".dict")) ) {
    		continue;
    	}
    	
    	dict_dir.SetFullName(name);
    	
    	// extract file
    	wxFFileOutputStream out_stream(dict_dir.GetFullPath());
    	
    	wxFSFile* file = vfs.OpenFile(_T("file:///")+ zippath+_T("#zip:") + name);
    	if (file) {
    		wxInputStream* stream = file->GetStream();
    		
    		out_stream.Write(*stream);
    		
    		// install dictionary
    	    install_dict(dict_dir.GetFullName());
    	    
    		delete file;
    	}
    	
    	delete p_entry;
    }

    reinit_installed_dict_info();
	create_all_dicts_dbconn();
}


bool DictManager::swap_dicts_order(const wxString& filename1, const int order1, \
		const wxString& filename2, const int order2) {
	wxLogMessage(_T("DictManager::swap_dicts_order"));
	
	try {   
		wxString sql;
		// remove dict id in words index info
	    sql.Printf(_T(" \
	    		UPDATE dicts \
	    			SET xorder=%d \
	    			WHERE filename=\'%s\'; \
	    		"), order1, filename2.c_str()
	    );
	    // wxMessageBox(sql);
	    _dictmgr_db.ExecuteUpdate(sql);
	    
	    sql.Printf(_T(" \
	    		UPDATE dicts \
	    			SET xorder=%d \
	    			WHERE filename=\'%s\'; \
	    		"), order2, filename1.c_str()
	    );
	    // wxMessageBox(sql);
	    _dictmgr_db.ExecuteUpdate(sql);
	    // wxMessageBox(sql);	    
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		return false;
	}

	return true;
}


bool DictManager::append_words_to_listctrl(const wxString& word, wxListView* listctrl) {
	wxLogMessage(_T("DictManager::append_words_to_listctrl"));
	_current_word = word;
	wxString s_word = word;
	s_word.Replace(_T("\'"), _T("\'\'"));
	s_word.MakeLower();
	
	//dict_dir.SetFullName(filename);
	//wxRemoveFile(dict_dir.GetFullPath());
	listctrl->DeleteAllItems();
	
	try {
	    // 
		wxString sql;
		sql.Printf(_T(" \
	    		SELECT words.word \
	    			FROM words \
	    			WHERE words.lword GLOB \'%s*\' \
					LIMIT 100; \
	    		"), s_word.c_str()
	    );
	    wxSQLite3ResultSet dicts_ret = _dictmgr_db.ExecuteQuery(sql);
	    while (dicts_ret.NextRow()) {
	    	listctrl->InsertItem(listctrl->GetItemCount(), dicts_ret.GetString(0), 0);
	    }
#ifdef __WXMSW__
	    listctrl->SetColumnWidth(0, listctrl->GetClientSize().x);
#elif __WXGTK__
	    listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE);
#endif
	    
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		return false;
	}
	
	return true;	
}


bool DictManager::show_word_dicts(const wxString& word, wxListView* listctrl, wxHtmlWindow* explain_wnd) {
	wxLogMessage(_T("DictManager::show_word_dicts"));
	
	_current_word = word;
	wxString s_word = word;
	s_word.Replace(_T("\'"), _T("\'\'")); // without do this, db will crash

	listctrl->DeleteAllItems();
	
	try {
	    // 
		wxString sql;
		sql.Printf(_T(" \
	    		SELECT dicts.name, dicts.filename \
	    			FROM dicts, words \
	    			WHERE words.word=\'%s\' \
					AND words.dictids GLOB \'*|\'||dicts.id||\'|*\' \
					AND dicts.status=1 \
					ORDER BY dicts.xorder ASC \
					; \
	    		"), s_word.c_str()
	    );
	    wxSQLite3ResultSet dicts_ret = _dictmgr_db.ExecuteQuery(sql);
	    while (dicts_ret.NextRow()) {
	    	long i = listctrl->InsertItem(listctrl->GetItemCount(), dicts_ret.GetString(0), 0);
	    	// listctrl->SetItemPtrData(i, (wxUIntPtr)new wxString(dicts_ret.GetString(1))); // !WARNING! May have memory leak, i've no idea;
	    	listctrl->SetItemPtrData(i, (wxUIntPtr)_dict_query_hash[dicts_ret.GetString(1)]);
	    	//listctrl->Append(dicts_ret.GetString(0), new DictListData(dicts_ret.GetString(1)) );
	    }
#ifdef __WXMSW__
	    listctrl->SetColumnWidth(0, listctrl->GetClientSize().x);
#elif __WXGTK__
	    listctrl->SetColumnWidth(0, wxLIST_AUTOSIZE);
#endif
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		return false;
	}
	
	/*DictQuery* p_data = (DictQuery*) listctrl->GetItemData(0);
	if (NULL==p_data) {
		return false;
	}
	
	p_data->query(_current_word, explain_wnd);*/

	/*DictQueryHash::iterator it = _dict_query_hash.find(*p_data);
	if (it==_dict_query_hash.end()) {
		return false;
	}
	
	it->second->query(_current_word, explain_wnd);*/
	
	/*if (_dict_query_hash.find(p_data->filename)!=_dict_query_hash.end()) {
		wxMessageBox(_("bingo!"));
	}*/
	
	return true;
}


bool DictManager::show_dict_explain(wxListView* listctrl, int listindex, wxHtmlWindow* explain_wnd) {
	DictQuery* p_data = (DictQuery*) listctrl->GetItemData(listindex);
	if (NULL==p_data) {
		return false;
	}
	
	p_data->query(_current_word, explain_wnd);
}


bool DictManager::uninstall_dict(const wxString& filename) {
	wxLogMessage(_T("DictManager::uninstall_dict"));
	
	//dict_dir.SetFullName(filename);
	//wxRemoveFile(dict_dir.GetFullPath());
	
	try {
	    // get installed id
		wxString uninstall_sql;
		uninstall_sql.Printf(_T(" \
	    		SELECT dicts.id \
	    			FROM dicts \
	    			WHERE dicts.filename=\'%s\'; \
	    		"), filename.c_str()
	    );
	    wxSQLite3ResultSet dicts_ret = _dictmgr_db.ExecuteQuery(uninstall_sql);
	    dicts_ret.NextRow();
	    int id = dicts_ret.GetInt(0,-1);
	    // wxMessageBox(wxString::Format(_T("%d"), id));
	    dicts_ret.Finalize();
	    
		// remove dict id in words index info
		uninstall_sql.Printf(_T(" \
	    		UPDATE words \
	    			SET dictids=replace(dictids, \'|%d|\', \'|\') \
	    			WHERE words.dictids GLOB \'*|%d|*\' ; \
	    		"), id, id
	    );
	    // wxMessageBox(uninstall_sql);
	    _dictmgr_db.ExecuteUpdate(uninstall_sql);
	    // wxMessageBox(uninstall_sql);
	    
		uninstall_sql.Printf(_T("DELETE FROM dicts \
				WHERE filename = \'%s\' ;") ,
				filename.c_str()
		);
	    //wxMessageBox(uninstall_sql);
	    _dictmgr_db.ExecuteUpdate(uninstall_sql);
	    //wxMessageBox(uninstall_sql);
	    
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		return false;
	}
		
	return true;
}


bool DictManager::disable_dict(const wxString& filename) {
	wxLogMessage(_T("DictManager::disable_dict"));
	
	try {
		// remove dict id in words index info
	    wxString disable_sql;
	    disable_sql.Printf(_T(" \
	    		UPDATE dicts \
	    			SET status=-1 \
	    			WHERE filename=\'%s\'; \
	    		"), filename.c_str()
	    );
	    // wxMessageBox(disable_sql);
	    _dictmgr_db.ExecuteUpdate(disable_sql);
	    // wxMessageBox(disable_sql);	    
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		return false;
	}

	return true;
}


bool DictManager::enable_dict(const wxString& filename){
	wxLogMessage(_T("DictManager::enable_dict"));
	
	try {	    
		// remove dict id in words index info
	    wxString enable_sql = wxString::Format(_T(" \
	    		UPDATE dicts \
	    			SET status=1 \
	    			WHERE filename=\'%s\'; \
	    		"), filename.c_str()
	    );
	    // wxMessageBox(enable_sql);
	    _dictmgr_db.ExecuteUpdate(enable_sql);
	    // wxMessageBox(enable_sql);	    
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		return false;
	}
	
	return true;
}


bool DictManager::maintains_mgrdb() {
	wxLogMessage(_T("DictManager::maintains_mgrdb"));
	
	try { // VACUUM; 
	    _dictmgr_db.ExecuteUpdate(_T("DELETE FROM words \
				WHERE dictids = \'|\' ; \
	    		"
		));
	    
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		return false;
	}
	
	return true;
}

bool DictManager::install_dict(const wxString& filename) {
	wxLogMessage(_T("DictManager::install_dict"));
	
	dict_dir.SetFullName(filename);
	
	
	try {
		wxString install_sql;
		install_sql.Printf(_T("ATTACH DATABASE \'%s\' AS %s"), 
				dict_dir.GetFullPath().c_str(), dict_dir.GetName().c_str());
	    _dictmgr_db.ExecuteUpdate(install_sql);
		//wxMessageBox(filename);
	    
	    // add info to mgrdb.dicts
	    install_sql.Printf(_T(" \
	    		INSERT INTO main.dicts (name, filename, status) \
	    			SELECT %s.dictinfo.name, \'%s\', 1 \
	    			FROM %s.dictinfo; \
	    		"), dict_dir.GetName().c_str(), filename.c_str(), dict_dir.GetName().c_str()
	    );
	    _dictmgr_db.ExecuteUpdate(install_sql);

	    // get installed id
	    install_sql.Printf(_T(" \
	    		SELECT main.dicts.id \
	    			FROM main.dicts \
	    			WHERE main.dicts.filename=\'%s\'; \
	    		"), filename.c_str()
	    );
	    wxSQLite3ResultSet dicts_ret = _dictmgr_db.ExecuteQuery(install_sql);
	    dicts_ret.NextRow();
	    int id = dicts_ret.GetInt(0,0);
	    dicts_ret.Finalize();
	    
	    // update already exist words
	    install_sql.Printf(_T(" \
	    		UPDATE main.words \
	    			SET dictids=dictids||%d||\'|\' \
	    			WHERE main.words.word IN ( \
	    				SELECT %s.wordslist.word \
	    				FROM %s.wordslist ); \
	    		"), id, dict_dir.GetName().c_str(), dict_dir.GetName().c_str()
	    );
	    // wxMessageBox(install_sql);
	    _dictmgr_db.ExecuteUpdate(install_sql);
	    // wxMessageBox(install_sql);
	    
	    // insert words which does not exist
	    install_sql.Printf(_T(" \
	    		INSERT INTO main.words (word, lword, dictids) \
	    			SELECT %s.wordslist.word, lower(%s.wordslist.word), \'|\'||%d||\'|\' \
	    			FROM %s.wordslist \
	    			WHERE %s.wordslist.word not in (SELECT main.words.word FROM main.words); \
	    		"), dict_dir.GetName().c_str(), dict_dir.GetName().c_str(), id, dict_dir.GetName().c_str(), dict_dir.GetName().c_str()
	    );
	    // wxMessageBox(install_sql);
	    _dictmgr_db.ExecuteUpdate(install_sql);
	    // wxMessageBox(install_sql);
	    
	    //_dictmgr_db.Commit();

		install_sql = wxString::Format(_T("DETACH DATABASE %s"), 
				dict_dir.GetName().c_str());
	    //wxMessageBox(install_sql);
	    _dictmgr_db.ExecuteUpdate(install_sql);
	    //wxMessageBox(install_sql);
	    
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		__DAO_FATAL_ERROR__;
	}

	return true;
}


void DictManager::reinit_installed_dict_info() {
    installed_dict_names.Clear();
	installed_dict_filenames.Clear();
    installed_dict_status.Clear();
	installed_dict_xorder.Clear();
	
	try {
	    wxSQLite3ResultSet dicts_ret = _dictmgr_db.ExecuteQuery(
	    		_T("SELECT count(filename) FROM dicts;")
	            );
	    dicts_ret.NextRow();
	    installed_dict_count = dicts_ret.GetInt(0);
	}
	catch (wxSQLite3Exception& e) {
		installed_dict_count = 0;
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		__DAO_FATAL_ERROR__;
	}
	
	try {
	    wxSQLite3ResultSet dicts_ret = _dictmgr_db.ExecuteQuery(
	    		_T("SELECT name, filename, status, xorder FROM dicts ORDER BY xorder ASC;")
	            );
	    while (dicts_ret.NextRow()) {
	        installed_dict_names.Add(dicts_ret.GetString(0));
	    	installed_dict_filenames.Add(dicts_ret.GetString(1));
	        installed_dict_status.Add(dicts_ret.GetInt(2));
	        installed_dict_xorder.Add(dicts_ret.GetInt(3));
	    }
	} catch (wxSQLite3Exception& e) {
		wxLogError(_T("%s: %s, %d"), __DAO_FUNC_NAME__.c_str(), e.GetMessage().c_str(), e.GetErrorCode());
		__DAO_FATAL_ERROR__;
	}
}


DictManager::~DictManager() {
	delete_all_dicts_dbconn();
	wxLogMessage(_T("DictManager::~DictManager"));
}

// DictManager::install_all_dicts(const wxString& path) {}

DictManager* DictManager::_dict_mgr = NULL;

